﻿using System;
using System.Collections.Generic;

using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System.IO;
using System.ComponentModel;

namespace SceneEngine
{
    public delegate void ChangedEventHandler(object sender, EventArgs e);

    public class GroupObject
    {
        private Vector3 position;
        private Quaternion rotation = Quaternion.Identity;
        private float scale;

        private BoundingBox bounds;

        private Vector3[] deltaPosition;

        private bool rotatePerObject = true;
        private bool worldOrigin = true;

        private Vector3 origin;

        private List<string> objects;

        public event ChangedEventHandler Changed;

        public int Count
        {
            get { return objects.Count; }
        }

        public Vector3[] DeltaPosition
        {
            get { return deltaPosition; }
        }

        public Vector3 Position
        {
            get { return position; }
            set
            {
                UpdatePositions(value - position);
                position = value;
            }
        }
        public Quaternion Rotation
        {
            get { return rotation; }
            set { rotation = value; }
        }
        public float Scale
        {
            get { return scale; }
            set { scale = value; }
        }

        public BoundingBox Bounds
        {
            get { return bounds; }
        }

        public Vector3 RotationOrigin
        {
            get { return origin; }
        }

        public bool RotatePerObject
        {
            get { return rotatePerObject; }
            set { rotatePerObject = value; }
        }
        public bool WorldOrigin
        {
            get { return worldOrigin; }
            set
            { 
                worldOrigin = value;

                if (worldOrigin == false)
                {
                    origin = position;
                }
                else
                {
                    origin = Vector3.Zero;
                }
            }
        }

        public List<string> Objects
        {
            get { return objects; }
        }

        /// <summary>
        /// Constructs an empty group of BaseObjects.
        /// </summary>
        public GroupObject()
        {
            objects = new List<string>();

            scale = 1.0f;
            SetUpBounds();
        }

        /// <summary>
        /// Updates the groups bounds to fit each object in.
        /// </summary>
        private void SetUpBounds()
        {
            bounds = new BoundingBox();

            foreach (string name in objects)
            {
                if (bounds != new BoundingBox())
                {
                    bounds = BoundingBox.CreateMerged(bounds, 
                        EngineGlobals.Scene.GetBaseObject(name).Bounds);
                }
                else
                {
                    bounds = EngineGlobals.Scene.GetBaseObject(name).Bounds;
                }
            }

            position = Vector3.Lerp(bounds.Min, bounds.Max, 0.5f);

            if (worldOrigin == false)
            {
                origin = position;
            }
        }

        /// <summary>
        /// Triggers the Changed event when the objects collection changes.
        /// </summary>
        protected virtual void OnChanged(EventArgs e)
        {
            if (Changed != null)
                Changed(this, e);

            deltaPosition = new Vector3[objects.Count];
        }

        /// <summary>
        /// Moves all of the objects in the group by an amount.
        /// </summary>
        private void UpdatePositions(Vector3 amount)
        {
            if (amount == Vector3.Zero)
            {
                return;
            }

            for (int i = 0; i < objects.Count; i++)
            {
                BaseObject entity = EngineGlobals.Scene.GetBaseObject(objects[i]);
                entity.Move(amount);
            }

            bounds.Min += amount;
            bounds.Max += amount;

            position += amount;

            if (worldOrigin == false)
            {
                origin = position;
            }
        }
        /// <summary>
        /// Rotates all of the objects in the group by an amount.
        /// </summary>
        public void UpdateRotations(Quaternion amount, bool localRotation)
        {
            for (int i = 0; i < objects.Count; i++)
            {
                BaseObject entity = EngineGlobals.Scene.GetBaseObject(objects[i]);

                if (rotatePerObject)
                {
                    if (localRotation)
                    {
                        entity.Rotation *= amount;
                    }
                    else
                    {
                        entity.Rotation = amount * entity.Rotation;
                    }

                    continue;
                }

                Matrix translation = Matrix.CreateTranslation(entity.Position - origin) *
                    Matrix.CreateFromQuaternion(amount);

                Vector3 deltaP = (translation.Translation + origin) - entity.Position;

                entity.Rotate(amount, localRotation);
                entity.Move(deltaP);

                deltaPosition[i] += deltaP;
            }

            if (localRotation)
            {
                rotation *= amount;
            }
            else
            {
                rotation = amount * rotation;
            }
        }
        /// <summary>
        /// Rotates all of the objects in the group by an amount.
        /// </summary>
        public void UpdateScale(float amount)
        {
            for (int i = 0; i < objects.Count; i++)
            {
                BaseObject entity = EngineGlobals.Scene.GetBaseObject(objects[i]);
                entity.Scale += new Vector3(amount);
            }

            scale += amount;
        }

        /// <summary>
        /// Forces all the objects in the group to update their bounds.
        /// </summary>
        public void UpdateBounds()
        {
            foreach (string name in objects)
            {
                BaseObject entity = EngineGlobals.Scene.GetBaseObject(name);
                entity.SetUpBounds();
            }

            SetUpBounds();
        }

        /// <summary>
        /// Resets the groups rotation.
        /// </summary>
        public void ResetRotation()
        {
            rotation = Quaternion.Identity;

            for (int i = 0; i < deltaPosition.Length; i++)
            {
                deltaPosition[i] = Vector3.Zero;
            }
        }
        /// <summary>
        /// Resets the groups rotation.
        /// </summary>
        public void ResetScale()
        {
            scale = 1.0f;
        }

        /// <summary>
        /// Removes all the objects from the group.
        /// </summary>
        public void Clear()
        {
            objects.Clear();

            bounds = new BoundingBox();
            OnChanged(EventArgs.Empty);
        }

        /// <summary>
        /// Adds a single object to the group.
        /// </summary>
        public void Add(string name)
        {
            objects.Add(name);

            if (bounds != new BoundingBox())
            {
                bounds = BoundingBox.CreateMerged(bounds, 
                    EngineGlobals.Scene.GetBaseObject(name).Bounds);
            }
            else
            {
                bounds = EngineGlobals.Scene.GetBaseObject(name).Bounds;
            }

            if (worldOrigin == false)
            {
                origin = position;
            }

            position = Vector3.Lerp(bounds.Min, bounds.Max, 0.5f);

            OnChanged(EventArgs.Empty);
        }
        /// <summary>
        /// Adds a range of objects to the group.
        /// </summary>
        public void AddRange(List<string> names)
        {
            objects.AddRange(names);

            foreach (string name in names)
            {
                if (bounds != new BoundingBox())
                {
                    bounds = BoundingBox.CreateMerged(bounds, 
                        EngineGlobals.Scene.GetBaseObject(name).Bounds);
                }
                else
                {
                    bounds = EngineGlobals.Scene.GetBaseObject(name).Bounds;
                }
            }

            if (worldOrigin == false)
            {
                origin = position;
            }

            position = Vector3.Lerp(bounds.Min, bounds.Max, 0.5f);

            OnChanged(EventArgs.Empty);
        }
        /// <summary>
        /// Removes an object from the group.
        /// </summary>
        public void Remove(string name)
        {
            objects.Remove(name);

            SetUpBounds();
            OnChanged(EventArgs.Empty);
        }

        public void ChangeName(int index, string newValue)
        {
            objects[index] = newValue;
            SetUpBounds();
        }

        public void Update()
        {

        }
    }
}
